package com.seckill.service.impl;

import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.DigestUtils;

import com.seckill.dto.Exposer;
import com.seckill.dto.SeckillExecution;
import com.seckill.entity.Seckill;
import com.seckill.entity.SuccessKilled;
import com.seckill.enums.SeckillStateEnum;
import com.seckill.exception.RepeatKillException;
import com.seckill.exception.SeckillCloseException;
import com.seckill.exception.SeckillException;
import com.seckill.repository.SeckillRepository;
import com.seckill.repository.SuccessKilledRepository;
import com.seckill.repository.cache.RedisRepository;
import com.seckill.service.SeckillService;

@Service
public class SeckillServiceImpl implements SeckillService {

	private final Logger logger = LoggerFactory.getLogger(this.getClass());

	@Autowired
	private SeckillRepository seckillRepository;

	@Autowired
	private SuccessKilledRepository successKilledRepository;

	@Autowired
	private RedisRepository redisRepository;

	public final String salt = "abd&$#%ia*()*(_IAGasdBSI_+)DAGI";

	@Override
	public List<Seckill> getSeckillList() {

		return seckillRepository.queryAll(0, 4);
	}

	@Override
	public Seckill getById(long seckillId) {
		return seckillRepository.queryById(seckillId);
	}

	/**
	 * 暴漏秒杀的接口地址
	 * 
	 */
	@Override
	public Exposer exportSeckillUrl(long seckillId) {
		// 若seckillId不存在 返回失败
		Seckill seckill = redisRepository.getSeckill(seckillId);
		if (seckill == null) {
			seckill = seckillRepository.queryById(seckillId);
			if (seckill == null) {
				return new Exposer(false, seckillId);
			} else {
				redisRepository.putSeckill(seckill);
			}
		}
		// 若当前时间不再秒杀时间内返回 当前时间、秒杀开始时间、秒杀结束时间
		long startTime = seckill.getStartTime().getTime();
		long endTime = seckill.getEndTime().getTime();
		long now = new Date().getTime();
		if (now < startTime || now > endTime) {
			return new Exposer(false, seckillId, now, startTime, endTime);
		}
		// 若符合秒杀条件，返回md5
		String md5 = this.getMD5(seckillId);
		return new Exposer(true, md5, seckillId);
	}

	/**
	 * 使用注解控制事务方法的优点： 1：开发团队达成一致的约定，明确标注事务方法的编程风格
	 * 2：保证每个事务方法的执行时间尽可能短，不要穿插其他的网络操作，或将其剥离至事务外部 3：不是所有的方法都需要事务：如只有一条修改操作，或者只读操作
	 */
	/**
	 * 此处修改sql顺序，先插入购买明细，在减库存，可以减少行级锁的持有时间。
	 */

	@Override
	@Transactional
	public SeckillExecution executeSeckill(long seckillId, long phone, String md5)
			throws RepeatKillException, SeckillCloseException, SeckillException {
		try {
			// md5是否被篡改
			if (StringUtils.isEmpty(md5) || !StringUtils.equals(md5, this.getMD5(seckillId))) {
				throw new SeckillException("seckill data rewrite");
			}

			// 更新购买记录 若失败抛出重复秒杀
			int insertCount = successKilledRepository.insertSuccessKilled(seckillId, phone);
			if (insertCount <= 0) {
				throw new RepeatKillException("seckill is repeated");
			}
			// 减库存，若失败抛出秒杀关闭
			Date now = new Date();
			int updateCount = seckillRepository.reduceNumber(seckillId, now);

			if (updateCount <= 0) {
				throw new SeckillCloseException("seckill is closed");
			}

			// 秒杀成功
			SuccessKilled successKilled = successKilledRepository.queryByIdwithSeckill(seckillId, phone);
			return new SeckillExecution(seckillId, SeckillStateEnum.SUCCESS, successKilled);
		} catch (SeckillCloseException e) {
			throw e;
		} catch (RepeatKillException e) {
			throw e;
		} catch (Exception e) {
			logger.error(e.getMessage(), e);
			// 将编译期异常转化为运行期异常，用于Spring回滚事务
			throw new SeckillException("seckill inner error : " + e.getMessage());
		}

	}

	private String getMD5(long seckillId) {
		String base = seckillId + "/" + salt;
		return DigestUtils.md5DigestAsHex(base.getBytes());
	}

	/**
	 * 使用存储过程 1：可以减少网络消耗,减少行级锁持有时间 2：不要过于依赖存储过程 3：简单逻辑可以使用存储过程 4：QPS：一个秒杀单6000/qps
	 */
	@Override
	public SeckillExecution executeSeckillProcedure(long seckillId, long phone, String md5) {
		// md5是否被篡改
		if (StringUtils.isEmpty(md5) || !StringUtils.equals(md5, this.getMD5(seckillId))) {
			throw new SeckillException("seckill data rewrite");
		}
		Date killTime = new Date();
		Map<String, Object> paramMap = new HashMap<String, Object>();
		paramMap.put("seckillId", seckillId);
		paramMap.put("phone", phone);
		paramMap.put("killTime", killTime);
		paramMap.put("result", null);
		try {
			seckillRepository.executeSeckillByProcedure(paramMap);
			int result = MapUtils.getInteger(paramMap, "result", -2);
			if(result == 1) {
				SuccessKilled successKilled = successKilledRepository.queryByIdwithSeckill(seckillId, phone);
				return new SeckillExecution(seckillId, SeckillStateEnum.SUCCESS, successKilled);
			}
			return new SeckillExecution(seckillId, SeckillStateEnum.stateOf(result));
		} catch (Exception e) {
			logger.error(e.getMessage(), e);
			return new SeckillExecution(seckillId, SeckillStateEnum.INNER_ERROR);
		}
	}
}
